package com.swak.nio_udp.channel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.DatagramChannel;
import java.nio.channels.SelectableChannel;
import java.nio.channels.spi.SelectorProvider;

import io.netty.channel.ChannelException;
import io.netty.util.internal.SocketUtils;
import lombok.Getter;

@Getter
public class NioDatagramChannel {

	private static final SelectorProvider DEFAULT_SELECTOR_PROVIDER = SelectorProvider.provider();

	private static DatagramChannel newSocket(SelectorProvider provider) {
		try {
			/**
			 *  Use the {@link SelectorProvider} to open {@link SocketChannel} and so remove condition in
			 *  {@link SelectorProvider#provider()} which is called by each DatagramChannel.open() otherwise.
			 *
			 *  See <a href="https://github.com/netty/netty/issues/2308">#2308</a>.
			 */
			return provider.openDatagramChannel();
		} catch (IOException e) {
			throw new ChannelException("Failed to open a socket.", e);
		}
	}

	private final SelectableChannel ch;

	public NioDatagramChannel() {
		this.ch = newSocket(DEFAULT_SELECTOR_PROVIDER);
	}

	protected DatagramChannel javaChannel() {
		return (DatagramChannel) ch;
	}

	protected SocketAddress localAddress0() {
		return javaChannel().socket().getLocalSocketAddress();
	}

	protected SocketAddress remoteAddress0() {
		return javaChannel().socket().getRemoteSocketAddress();
	}

	protected void doBind(SocketAddress localAddress) throws Exception {
		SocketUtils.bind(javaChannel(), localAddress);
	}

	protected void doConnect(SocketAddress remoteAddress) throws Exception {
		javaChannel().connect(remoteAddress);
	}
	
    protected void doDisconnect() throws Exception {
        javaChannel().disconnect();
    }
    
    protected void doClose() throws Exception {
        javaChannel().close();
    }
    
    protected void doWriteMessage(byte[] msg, SocketAddress remoteAddress) throws IOException {
    	ByteBuffer nioData = ByteBuffer.allocate(1024);
    	nioData.put(msg);
    	javaChannel().send(nioData, remoteAddress);
    }

	public static void main(String[] args) throws Exception {

		/*
		 *  测试 bind 和 connect 之前的一些操作
		 */
		NioDatagramChannel channel = new NioDatagramChannel();
		System.out.println(channel.localAddress0());
		System.out.println(channel.remoteAddress0());

		System.out.println("########## Bind ##########");
		channel.doBind(new InetSocketAddress("127.0.0.1", 8089));
		System.out.println(channel.localAddress0());
		System.out.println(channel.remoteAddress0());

		
		// 测试 1 ： 如果已经连接，如果给其他的地址发送消息，则会抛出异常(只能给指定的地址发送数据)
		System.out.println("########## Connect1(只能连接一次) ##########");
		channel.doConnect(new InetSocketAddress("127.0.0.1", 20003));
		System.out.println(channel.localAddress0());
		System.out.println(channel.remoteAddress0());
		
//		System.out.println("########## 写数据 (如果已经连接则只能写固定的地址) ##########");
		channel.doWriteMessage("Hello".getBytes(), new InetSocketAddress("127.0.0.1", 20003));
		
//		// 测试 2 ： 不连接到其他的服务器。直接给多个服务器发送数据 (不会有问题)
//		System.out.println("########## 写数据 (如果已经连接则只能写固定的地址) ##########");
//		channel.doWriteMessage("Hello".getBytes(), new InetSocketAddress("127.0.0.1", 2002));
//		channel.doWriteMessage("Hello".getBytes(), new InetSocketAddress("127.0.0.1", 2003));
//		channel.doWriteMessage("Hello".getBytes(), new InetSocketAddress("127.0.0.1", 2004));
//		

//		System.out.println("########## Connect2 ##########");
//		channel.doConnect(new InetSocketAddress("127.0.0.1", 20004));
//		System.out.println(channel.localAddress0());
//		System.out.println(channel.remoteAddress0());

	}
}
